from __future__ import with_statement

import cgi
import os.path
import re

from PyQt4 import QtCore, QtGui, Qsci

from ui_CodeWidget import Ui_CodeWidget
from import_se_dialog import ImportSEDialog

from app import Application
import os_helpers
import qt_helpers

# TODO: Store window size, window position, splitter position in user preferences

class CodeWidget(QtGui.QMainWindow, Ui_CodeWidget):
    def __init__(self, parent, app, user_prefs, environment_list):
        QtGui.QMainWindow.__init__(self, parent)
        self.setupUi(self)

        self.configure_editor()

        self.CodeEditPosLabel = QtGui.QLabel(self)
        self.statusBar().addWidget(self.CodeEditPosLabel, 1)

        self.LanguageCombo.currentIndexChanged[str].connect(self.language_changed)
        self.EnvironmentCombo.currentIndexChanged[str].connect(self.environment_changed)
        self.VersionCombo.currentIndexChanged[int].connect(self.version_changed)
        self.ArgumentsEdit.textChanged[str].connect(self.arguments_changed)
        self.CodeEdit.textChanged.connect(self.update_copy_state)
        self.CodeEdit.copyAvailable.connect(self.update_copy_names)
        self.CodeEdit.cursorPositionChanged.connect(self.update_code_edit_pos)
        self.actionAbout.triggered.connect(self.about)
        self.actionCopyAsHtml.triggered.connect(self.copy_as_html)
        self.actionCopyAsMarkdown.triggered.connect(self.copy_as_markdown)
        self.actionExit.triggered.connect(self.close)
        self.actionImportSE.triggered.connect(self.import_se)
        self.actionRun.triggered.connect(self.run)

        self.app = app
        self.user_prefs = user_prefs
        self.environment_list = environment_list

        self.code_dir = os.path.join(app.user_dir, "code")
        os_helpers.mkdir_p(self.code_dir)

        self.LanguageCombo.blockSignals(True)
        for language in sorted(environment_list.keys()):
            self.LanguageCombo.addItem(language)
        # QComboBox defaults to first added item.
        # Reset that so we can properly invoke the signal later.
        # (Both here and in various ..._changed slots.)
        self.LanguageCombo.setCurrentIndex(-1)
        self.LanguageCombo.blockSignals(False)

        try:
            self.LanguageCombo.setCurrentIndex(
                self.LanguageCombo.findText(self.user_prefs["default_language"]))
        except KeyError:
            pass
        if self.LanguageCombo.currentIndex() == -1:
            self.LanguageCombo.setCurrentIndex(0)
            
        self.CodeEdit.setFocus()
    
    def configure_editor(self):
        """
        Configures the QScintilla widget.
        
        Eventually we could add some logic for configuring from user_prefs.
        """
        self.CodeEdit.setEolMode(Qsci.QsciScintilla.EolUnix)
        self.CodeEdit.setAutoIndent(True)
        self.CodeEdit.setIndentationWidth(4)
        # Using tab characters is questionable in general and is particularly
        # bad for Python.
        self.CodeEdit.setIndentationsUseTabs(False)
    
    def closeEvent(self, event):
        self.save_code()
    
    def current_language(self):
        """Gets the text name of the currently selected language."""
        return str(self.LanguageCombo.currentText())

    def current_environment_name(self):
        """Gets the text name of the currently selected environment."""
        return str(self.EnvironmentCombo.currentText())

    def current_environment(self):
        """Gets the Environment object of the currently selected environment."""
        return self.VersionCombo.itemData(self.VersionCombo.currentIndex()).toPyObject()

    def language_changed(self, text):
        if self.CodeEdit.isModified():
            self.save_code()

        self.user_prefs["default_language"] = self.current_language()
        
        self.EnvironmentCombo.blockSignals(True)
        self.EnvironmentCombo.clear()
        for environment in sorted(self.environment_list[self.current_language()].keys()):
            self.EnvironmentCombo.addItem(environment)
        self.EnvironmentCombo.setCurrentIndex(-1)
        self.EnvironmentCombo.blockSignals(False)
            
        try:
            self.EnvironmentCombo.setCurrentIndex(
                self.EnvironmentCombo.findText(
                    self.user_prefs["default_environment"][self.current_language()]))
        except KeyError:
            pass
        if self.EnvironmentCombo.currentIndex() == -1:
            self.EnvironmentCombo.setCurrentIndex(0)
            
        if not self.load_code():
            self.clear_code()

    def environment_changed(self, text):
        self.user_prefs.set("default_environment", self.current_language(), self.current_environment_name())
            
        self.VersionCombo.blockSignals(True)    
        self.VersionCombo.clear()
        for environment in self.environment_list[self.current_language()][self.current_environment_name()]:
            self.VersionCombo.addItem(environment.version, environment)
        self.VersionCombo.setCurrentIndex(-1)
        self.VersionCombo.blockSignals(False)
            
        try:
            default_version = self.user_prefs["default_version"][self.current_language()][self.current_environment_name()]
            self.VersionCombo.setCurrentIndex(
                qt_helpers.find_data(self.VersionCombo, lambda x: x.version == default_version))
        except KeyError:
            pass
        if self.VersionCombo.currentIndex() == -1:
            self.VersionCombo.setCurrentIndex(self.VersionCombo.count() - 1)
            
        if not self.current_environment().has_arguments:
            # TODO: Would, e.g., enable / disable be a better UI than show / hide?
            self.ArgumentsLabel.setVisible(False)
            self.ArgumentsEdit.setVisible(False)
        else:
            self.ArgumentsLabel.setVisible(True)
            self.ArgumentsEdit.setVisible(True)
            try:
                self.ArgumentsEdit.setPlaceholderText("Optional command-line arguments for %s" % self.current_environment_name())
            except:
                # setPlaceholderText is not available in older Qt versions
                pass
            try:
                self.ArgumentsEdit.setText(self.user_prefs["arguments"][self.current_language()][self.current_environment_name()])
            except KeyError:
                self.ArgumentsEdit.setText("")
            
    def version_changed(self, index):
        self.user_prefs.set("default_version", self.current_language(), self.current_environment_name(), self.current_environment().version)
        
        self.CodeEdit.setLexer(self.current_environment().language.lexer(self.CodeEdit))

    def arguments_changed(self, index):
        self.user_prefs.set("arguments", self.current_language(), self.current_environment_name(), str(self.ArgumentsEdit.text()))

    def update_code_edit_pos(self, line, pos):
        self.CodeEditPosLabel.setText("%i, %i" % (line + 1, pos + 1))

    def update_copy_state(self):
        enabled = self.CodeEdit.text() != ""
        self.actionCopyAsHtml.setEnabled(enabled)
        self.actionCopyAsMarkdown.setEnabled(enabled)

    def update_copy_names(self, yes):
        """
        Triggered by CodeEdit.copyAvailable(bool yes).
        """
        if yes:
            self.actionCopyAsHtml.setText("Copy Selection as &HTML")
            self.actionCopyAsMarkdown.setText("Copy Selection as &Markdown")
        else:
            self.actionCopyAsHtml.setText("Copy Entire Code as &HTML")
            self.actionCopyAsMarkdown.setText("Copy Entire Code as &Markdown")

    def get_code_filename(self):
        # TODO: Restructure this to permit multiple instances running at once?
        return os.path.join(self.code_dir, 
                            "code%s" % self.current_environment().language.suffix)

    def clear_code(self):
        self.CodeEdit.setText("")
        self.CodeEdit.setModified(False)

    def save_code(self):
        """
        Saves the current code.
        
        MEHF auto-saves code, so this action currently isn't accessible from the
        UI.
        """
        try:
            with open(self.get_code_filename(), "wt") as file:
                file.write(self.CodeEdit.text())
        except IOError, e:
            QtGui.QMessageBox.critical(None, self.app.title, 
                                       "Failed to save_code code: %s" % str(e))

    def load_code(self):
        """
        Loads code for the current environment.
        
        Currently, to make the UI as quick and simple as possible, each language
        only has one piece of associated code.
        """
        try:
            with open(self.get_code_filename(), "rt") as file:
                self.CodeEdit.setText(file.read())
                return True
        except IOError:
            return False

    def run(self):
        """
        Compiles and runs the current code.
        """
        env = self.current_environment()
        self.save_code()
        self.messagesBrowser.setPlainText("")
        self.messagesBrowser.repaint()

        try:
            QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
            with os_helpers.LocalCwd(self.code_dir, env.host):        
                if env.needs_compilation():
                    result = env.compile(self.get_code_filename(),
                                         qt_helpers.TextEditAppender(self.messagesBrowser))
                    if not result:
                        qt_helpers.append_html(self.messagesBrowser, r'<p style="color: red">Failed.</p>')
                        return
                    # TODO: Only output "No problems found" if no messages were output?
                    qt_helpers.append_html(self.messagesBrowser, r'<p>No problems found.</p>')
                
                env.execute(self.get_code_filename())
        finally:
            QtGui.QApplication.restoreOverrideCursor()

    def about(self):
        """
        Help | About action.
        """
        qt_helpers.Fluent(QtGui.QMessageBox())\
            .setWindowTitle("About " + Application.title)\
            .setText("<p><b>%s v%s</b></p>"
                     "<p>Copyright &copy; 2010-2012 Josh Kelley.  See LICENSE.txt for license.</p>"
                     "<p><a href=\"%s\">%s</a></p>"
                     % (Application.title, Application.version, Application.url, Application.url))\
            .exec_()

    def import_se(self):
        import_se = ImportSEDialog(self, self.environment_list, self.current_language())
        if not import_se.exec_():
            return
        
        language_index = self.LanguageCombo.findText(import_se.language,
                                                     QtCore.Qt.MatchFixedString)
        if language_index != -1:
            self.LanguageCombo.setCurrentIndex(language_index)
            
        self.CodeEdit.setText(import_se.code)

    def copy_as_html(self):
        if self.CodeEdit.hasSelectedText():
            text = self.CodeEdit.selectedText()
        else:
            text = self.CodeEdit.text()
        text = "<pre><code>" + cgi.escape(str(text)) + "</code></pre>"
        QtGui.QApplication.clipboard().setText(text)
    
    def copy_as_markdown(self):
        if self.CodeEdit.hasSelectedText():
            text = self.CodeEdit.selectedText()
        else:
            text = self.CodeEdit.text()
        text = re.sub(r'(^|\n)', r'\1    ', str(text))
        text = re.sub('    $', '', text)
        QtGui.QApplication.clipboard().setText(text)
